GUID: 2653889r GitHub URL: https://github.com/Hrisity/HrisityAI.github.io.git

Generating Chorales With RNN

Getting the Data

The following code snippets are part of a process that generates chorales using Recurrent Neural Networks (RNNs). I will break down each piece of code, so their function can be understood:

1. Downloading and Extracting the Data

This first bit of code is used to download and extract a dataset. With ‘import tensorflow as tf’ the TensorFlow library, which is a popular machine learning framework, is imported. Then, the function ‘tf.keras.utils.get_file(…)’ helps in downloading a file from a URL. ‘jsb_chorales.tgz’ is the name of the file, and the URL afterwards is where the is downloaded from. The ‘cache_dir=“.”’ is specifying where the file will be stored, as ‘“.”’ means the current directory. The last line indicates that the downloaded file should be automatically extracted.

2. Locating the Data Files

In this step, the data files within the dataset are both located and sorted. ‘from pathlib import Path’ aims to import ‘Path’ class from the ‘pathlib’ module for filesystem path operations. Then, a ‘Path’ object that leads to the directory where the chorales datasets are located is created. The ‘glob(…)’ method is put in place to find the files matching a certain pattern. In this case, the variables: ‘train_files’, ‘valid_files’, and ‘testfiles’ store lists of paths to the training, validation, and test datasets, respectively. The chorale*.csv pattern selects CSV files matching this pattern in each subdirectory (‘train’, ‘valid’, and ‘test’).

3. Loading the Data

Here, the aim is to load the CSV files into a format that is suitable for the RNN. First, the pandas library, usually used for data manipulation and analysis, is imported. The function ‘load_chorales(filepaths)’ is defined to read the chorale files., while ‘pd.read_csv(filepath).values.tolist()’ reads the CSV files into a pandas DataFrame. Then, converts it into a NumPy array with ‘.values’, and later to a list with .tolist(). In brief, the function returns a list of lists. Each inner list represents a chorale. At the end of this bit of code, the variables are used to store the loaded chorales for training, validation, and testing.

Preparing the Data

The following 4 snippets of code are part of the"Preparing the Data" stage.

1. Preprocessing and Understanding the Notes Range

This bit of code creates a set of all unique notes present in the dataset. First, it creates a set to store the notes. Then, with the ‘ notes |= set(chord)’ the notes from each chord are added to the set. ’n_notes’ shows the total number of unique notes, while ‘min_note’ and ‘max_note’ calculate the minimum and maximum note values, excluding the value 0. It denotes silence. The assertions, in the end, check if the range of notes is as expected.

Code for Synthesiser

The following cell is code for a synthesiser to play MIDI. Not part of machine learning code to generate Bach, but useful for listening to the results and samples used for training!

2. Converting Notes to Sound and Playing Chords

The functions you just saw are quite exciting. At least I find them exciting. They convert musical notes into audible sounds that can be played.

The functions convert note numbers to frequencies. With ‘notes_to_frequencies’ sine waves are generated, with ‘frequencies_to_samples’ - samples for chords, while with ‘chords_to_samples’ ‘play_chords’ the chords are played. The code tests the synthesiser by playing the first three training chorales.

3. Preparing the Dataset for the RNN

This bit of code prepares the chorale dataset for training the RNN. The functions ‘create_target’ and ‘preprocess’ help in formatting correctly the data, while ‘bach_dataset’ create a TensorFlow dataset from the chorales. This results in preparing them for input into the RNN.

4. Creating Training, Validation, and Test Datasets

During this step, the training, validation, and test datasets using the bach_dataset function are initialised. The datasets used here are ‘train_set’, ‘valid_set’, and ‘test_set’. What is more, ‘shuffle_buffer_size=1000’ is also used in order for the training set to introduce randomness during training.

Building the Model

1. Constructing the Neural Network Model

This bit of code constructs the neural network model that is used for generating chorales. First, with embedding layer transforms the input notes into dense vectors of fixed size (n_embedding_dims=5). Four convolutional layers are used to help the model learn patterns across time. After each of these layers batch normalisation is applied. It stabilises and speeds up the model. The Long Short-Term Memory layer with 256 units improves the model’s ability to learn long-term dependencies in the data. At the end, the dense layer outputs a probability distribution over all possible notes.

This line aims to display a summary of the network model. ‘model.summary()’ prints out the details of each layer in the model including layer types, output shapes, and the number of parameters in each layer. This helps us understand the architecture of the model.

Training the Model

This bit of code actually encompasses all the crucial steps that are involved in training the model for creating chorales using Recurrent Neural Networks: defining the optimiser, compiling the model with the correct loss function and metrics, and then training the model on the training dataset while validating its performance on a separate validation dataset.

Saving and Evaluating Your Model

This line of code evaluates the performance of the model by using the dataset. First, with the ‘model.evaluate(…)’ function the performance is tested on the provided dataset (‘test_set’).

Generating Chorales

1. Chorale Generation Function

This function creates a new chorale with a seed sequence of chords. Key components here are ‘model’ (a trained neural network model used for generation), ‘seed_chords’ (a sequence of chords ), ‘length’ ( the desired length), and ‘temperature’ (a parameter controlling the randomness of note selection)

2. Preparing and Playing Seed Chords

The lines you just saw select a sequence of seed chords from the test chorales and play them. ‘seed_chords = test_chorales[2][:8]’ takes the first 8 chords of the third chorale in the test set, while the ‘play_chords(…)’ plays the seed chords that were selected.

3. Generating and Playing a New Chorale (Temperature: 0.8)

This bit of code, as it can be seen, generates a new chorale. However, this one has a lower temperature, which means that there is less randomness compared to the previous one.

4. Generating and Playing a New Chorale (Temperature: 1.0)

This code also generates a chorale but with a medium level of randomness.

5. Generating and Playing a New Chorale (Temperature: 1.5)

This bit of code generates a new chorale with a higher temperature, which again means more randomness and plays it. We can notice a chorale that has more variation and unpredictability in the note sequences. This is due to the higher temperature (thee function ‘generate_chorale_v2(..., temperature=1.5)”).